package com.fox.lock;



import com.fox.redisbase.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class DistributedLockServiceImpl implements DistributedLockService {
    private static final Logger logger = LoggerFactory.getLogger(DistributedLockServiceImpl.class);
    private static final String UNLOCK_LUA =
                   "if redis.call(\"get\",KEYS[1]) == KEYS[2]\n" +
                   "then\n" +
                   "    return redis.call(\"del\",KEYS[1])\n" +
                   "else\n" +
                   "    return -1\n" +
                   "end";
    private static final String RESET_EXPIRE =
                  "if redis.call(\"get\",KEYS[1]) == KEYS[2]\n" +
                  "then\n" +
                  "    redis.call(\"pexpire\",KEYS[1],KEYS[3]+redis.call(\"pttl\",KEYS[1]))\n" +
                  "    return redis.call(\"pttl\",KEYS[1])\n" +
                  "else\n" +
                  "    return 0\n" +
                  "end";
    private static final DefaultRedisScript<Long> unlockScript = new DefaultRedisScript<>();
    private static final DefaultRedisScript<Long> resetExpireScript = new DefaultRedisScript<>();

    static {
        unlockScript.setResultType(Long.class);
        unlockScript.setScriptText(UNLOCK_LUA);
        resetExpireScript.setResultType(Long.class);
        resetExpireScript.setScriptText(RESET_EXPIRE);

    }

    @Autowired
    //这里一定要<String,String>否则值会变成 "\"value\"" 导致用脚本删除的时候 redis.call("get",KEYS[1]) != KEYS[2]
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private RedisService redisService;

    //尝试获取锁直接返回成功或者失败 确保原子性
    //增加对redis超时的处理 超时也是获取锁失败
    @Override
    public boolean tryAquire(String lockKey, String requestId, long millis) {
        boolean result=false;
        try {
            result=redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, millis, TimeUnit.MILLISECONDS);
        }catch (RedisConnectionFailureException e){
            e.printStackTrace();
        }
        return result;
    }

    //多次尝试获取锁
    @Override
    public boolean tryLock(String lockKey, String requestId, long millis, int tryTimes) throws InterruptedException {
        logger.debug("lockName:{} requestId:{} tryTimes:{}",lockKey,requestId,tryTimes);
        while (tryTimes-- > 0) {
            if (tryAquire(lockKey, requestId, millis)) {
                return true;
            }
            Thread.sleep(redisService.getKeyExpire(lockKey, TimeUnit.MILLISECONDS));
        }
        return false;
    }

    //一直获取锁直到成功
    @Override
    public boolean lock(String lockKey, String requestId, long millis, long sleepMillis) throws InterruptedException {
        while (true) {
            if (tryAquire(lockKey, requestId, millis)) {
                return true;
            }
            Thread.sleep(sleepMillis);
        }
    }


    //锁重置超时时间
    @Override
    public long addExpire(String lockKey, String requestId, long millis){
        List<String> keyList = new ArrayList<>(3);
        keyList.add(lockKey);
        keyList.add(requestId);
        keyList.add(millis + "");
        return redisTemplate.execute(resetExpireScript, keyList);
    }

    //1表示释放了1个锁 0表示锁已经超时或被删除不需要释放 -1表示锁不是该线程的
    @Override
    public int tryRelease(String lockKey, String requestId) {
        List<String> keyList = new ArrayList(2);
        keyList.add(lockKey);
        keyList.add(requestId);
        return redisTemplate.execute(unlockScript, keyList).intValue();
    }


}
